<template>
  <div class="sql-editor">
    <div v-show="runLoading" class="run-loadings">
      <span class="el-icon-loading"></span>
      正在执行
    </div>
    <div class="header-tools">
      <ul class="tools-list">
        <li class="tools-item" @click="onRunCode">
          <i class="el-icon-video-play"></i>
          运行
        </li>
        <li class="tools-item" @click="onFormatSQL">
          <i class="el-icon-document"></i>
          格式化
        </li>
      </ul>
    </div>
    <codemirror ref="mySql" v-model="code" :options="sqlOptions"></codemirror>
  </div>
</template>

<script>
import CodeMirror from "codemirror";

// 核心样式
import "codemirror/lib/codemirror.css";

// 引入主题后还需要在 options 中指定主题才会生效
import "codemirror/addon/display/fullscreen.css"; // 全屏显示编辑器
import "codemirror/addon/display/fullscreen.js"; // 全屏显示编辑器

import "codemirror/theme/cobalt.css";
import "codemirror/theme/eclipse.css";
import "codemirror/theme/ayu-dark.css";
import "codemirror/theme/idea.css";
import "codemirror/theme/solarized.css";
import "codemirror/addon/hint/show-hint.css";
import "codemirror/addon/hint/show-hint.js";
import "codemirror/addon/hint/sql-hint.js";
import "codemirror/addon/selection/active-line.js";
import "codemirror/addon/edit/matchbrackets.js";
import "codemirror/mode/sql/sql";
import { format } from "sql-formatter";
import { mapGetters } from "vuex";

import { codemirror } from "vue-codemirror";

import * as api from "@/api/physicalData/physicalData";

export default {
  props: {
    value: {
      type: String,
      default: "",
    },
    runLoading: {
      type: Boolean,
      default: false,
    },
  },
  components: { codemirror },

  data() {
    return {
      splitRules: ["(", ")", ".", " ", ","],
      hintList: [
        {
          name: "count",
        },
        {
          name: "select",
        },
        {
          name: "from",
        },
        {
          name: "DATA",
        },
        {
          name: "LEFT JOIN",
        },
        {
          name: "[ from= ,to= ]",
        },
      ],
      sqlEditor: null,
      code: "",
      options: {
        lineNumbers: true,
        lineWrapping: true,
        styleActiveLine: true,
        extraKeys: "Ctrl",
        theme: "default",
      },
      sqlOptions: {
        tabSize: 4,
        lineNumbers: true,
        line: true,
        mode: "text/x-sql", // SQL SERVER
        smartIndent: true,
        indentUnit: 4,
        autoRefresh: true,
        theme: "default",
        lineWrapping: true,
        // 高亮行功能
        styleActiveLine: true,
        hight:'200',
        hintOptions: {
          completeSingle: false,
          hint: this.handleShowHint,
        },
      },
      hintLabel: "name",
    };
  },
  mounted() {
    this.$refs.mySql.codemirror.on("inputRead", (mySql) => {
      mySql.showHint();
    });
  },
  methods: {
    changeCode(code){
      this.code = code;
    },
    isArray(val) {
      return Object.prototype.toString.call(val) == "[object Array]";
    },

    getModeConf(editor, field) {
      return (
        editor.getModeAt(editor.getCursor()).config[field] ||
        CodeMirror.resolveMode("text/x-sql")[field]
      );
    },
    getKeywords(editor) {
      return this.getModeConf(editor, "keywords") || [];
    },
    getText(item) {
      return typeof item == "string" ? item : item.text;
    },
    match(string, word) {
      var len = string.length;
      var sub = this.getText(word).substr(0, len);
      return string.toUpperCase() === sub.toUpperCase();
    },
    objectOrClass(w, className) {
      if (typeof w === "object") {
        w.className = className;
      } else {
        w = { text: w, className: className };
      }
      return w;
    },
    addMatches(result, search, wordlist, formatter) {
      if (this.isArray(wordlist)) {
        for (var i = 0; i < wordlist.length; i++)
          if (this.match(search, wordlist[i]))
            result.push(formatter(wordlist[i]));
      } else {
        for (var word in wordlist)
          if (wordlist.hasOwnProperty(word)) {
            var val = wordlist[word];
            if (!val || val === true) {
              val = word;
            } else {
              val = val.displayText
                ? { text: val.text, displayText: val.displayText }
                : val.text;
            }
            if (this.match(search, val)) {
              result.push(formatter(val));
            }
          }
      }
    },
     // 提示框显示及样式
    hintRender(element, self, data) {
      let div = document.createElement("div");
      div.setAttribute("class", "autocomplete-div");

      // 添加弹出框表/字段图标
      let divIcon = ''
      if(data.type == 'table') {
        divIcon = document.createElement("i");
        divIcon.setAttribute("class", "el-icon-date");
        divIcon.style.color = 'blue'
      } else if(data.type == 'field') {
        divIcon = document.createElement("i");
        divIcon.setAttribute("class", "el-icon-film");
        divIcon.style.color = 'blue'
      } else {
        divIcon = document.createElement("i");
      }

      let divText = document.createElement("span");
      divText.setAttribute("class", "autocomplete-name");
      divText.innerText = data.displayText;
      divText.style.display = 'inline-block'
      divText.style.overflow = 'hidden'
      divText.style.whiteSpace = 'nowrap'
      divText.style.textOverflow = 'ellipsis'
      divText.style.marginRight = '10px'

      let divInfo = document.createElement("span");
      divInfo.setAttribute("class", "autocomplete-hint");
      divInfo.innerText = data.displayInfo;
      divInfo.style.display = 'inline-block'
      divInfo.style.float = 'right'
      divInfo.style.color = 'gray'
      divInfo.style.maxWidth = '150px'
      divInfo.style.overflow = 'hidden'
      divInfo.style.whiteSpace = 'nowrap'
      divInfo.style.textOverflow = 'ellipsis'

      div.appendChild(divIcon);
      div.appendChild(divText);
      div.appendChild(divInfo);
      element.appendChild(div);
    },
    async handleShowHint() {
      // 获取输入框实例
      const cmInstance = this.$refs.mySql.codemirror;
      // 得到光标
      let cursor = cmInstance.getCursor();
      // 得到行内容
      let cursorLine = cmInstance.getLine(cursor.line);
      // 得到光标位置
      let end = cursor.ch;
      this.list = [];
      // 得到光标标识
      let token = cmInstance.getTokenAt(cursor);

      this.getKeywordsHint(cursorLine, end, cmInstance);
      this.getListHint(cursorLine, end);
      const str = this.getKeyStr(cursorLine, end);
      if (!str) {
        return;
      }
      try {
        const res = await api.queryTipsByName({ name: str });
        const { columnName, refName, typeName } = res.data.data;
        const tipsArr = [...columnName, ...refName, ...typeName];
        columnName.forEach((item) => {
          if (this.list.indexOf(item) < 0) {
            this.list.push({
              text: item,
              displayText: item,
              displayInfo: 'columnName',
              render:this.hintRender
            });
          }
        });
        refName.forEach((item) => {
          if (this.list.indexOf(item) < 0) {
            this.list.push({
              text: item,
              displayText: item,
              displayInfo: 'refName',
              render:this.hintRender
            });
          }
        });
        typeName.forEach((item) => {
          if (this.list.indexOf(item) < 0) {
            this.list.push({
              text: item,
              displayText: item,
              displayInfo: 'typeName',
              render:this.hintRender
            });
          }
        });
      } catch (err) {}

      return {
        list: this.list, // 自定义提示内容
        // ch:选择提示内容替换的开始位置，line: 光标所在行
        from: { ch: token.start, line: cursor.line },
        to: { ch: token.end, line: cursor.line },
      };
    },
    getKeywordsHint(cursorLine, cursorIndex, cmInstance) {
      let indexList = [];
      const _that = this;
      for (let i = 0; i < cursorIndex; i++) {
        // 获取所有分隔符小于当前光标的位置
        if (this.splitRules.includes(cursorLine[i])) indexList.push(i);
      }
      // 得到当前距离光标最近且小于光标位置的字符位置
      const earlayRightIndex = indexList[indexList.length - 1];
      // 截取光标与最近且位置坐标小于光标的内容
      const str = cursorLine.substring(earlayRightIndex + 1, cursorIndex);
      // 遍历自定义提示数组，得到满足条件的提示内容
      const keywords = this.getKeywords(cmInstance);

      this.addMatches(this.list, str, keywords, function (w) {
        return _that.objectOrClass(w.toUpperCase(), "CodeMirror-hint-keyword");
      });
    },

    getKeyStr(cursorLine, cursorIndex) {
      let indexList = [];
      for (let i = 0; i < cursorIndex; i++) {
        // 获取所有分隔符小于当前光标的位置
        if (this.splitRules.includes(cursorLine[i])) indexList.push(i);
      }
      // 得到当前距离光标最近且小于光标位置的字符位置
      const earlayRightIndex = indexList[indexList.length - 1];
      // 截取光标与最近且位置坐标小于光标的内容
      const str = cursorLine.substring(earlayRightIndex + 1, cursorIndex);
      return str;
    },

    getListHint(cursorLine, cursorIndex) {
      let indexList = [];
      for (let i = 0; i < cursorIndex; i++) {
        // 获取所有分隔符小于当前光标的位置
        if (this.splitRules.includes(cursorLine[i])) indexList.push(i);
      }
      // 得到当前距离光标最近且小于光标位置的字符位置
      const earlayRightIndex = indexList[indexList.length - 1];
      // 截取光标与最近且位置坐标小于光标的内容
      const str = cursorLine.substring(earlayRightIndex + 1, cursorIndex);
      // 遍历自定义提示数组，得到满足条件的提示内容
      if (!str) {
        return;
      }

      this.hintList.forEach((item) => {
        if (
          item[this.hintLabel].indexOf(str) !== -1 ||
          item[this.hintLabel].indexOf(str.toUpperCase()) !== -1
        ) {
          this.list.push(item[this.hintLabel]);
        }
      });
    },

    onRunCode() {
      // 运行代码
      this.$emit("run", this.code);
    },
    onFormatSQL() {
      const cmInstance = this.$refs.mySql.codemirror;
      // 格式化代码
      if (cmInstance.getValue().trim() === "") {
        this.$message.warning("请先编辑 SQL 命令！");
        return;
      }
      const sqlCode = cmInstance.getValue();
      cmInstance.setValue(format(sqlCode));
    },
  },
};
</script>

<style>

</style>

<style lang="scss" scoped>
::v-deep .CodeMirror-hints {
  z-index: 3000 !important;
}
::v-deep .CodeMirror{
  height: 180px;
}
.sql-editor {
  position: relative;

  // 工具栏
  .header-tools {
    height: 40px;
    user-select: none;

    .tools-list {
      list-style: none;
      padding: 0 0 0 20px;
      margin: 0;
      display: flex;
      align-items: center;
      height: 100%;
      font-size: 12px;
      color: #666;

      .tools-item {
        margin: 0px 10px;
        cursor: pointer;

        &:hover {
          color: #999;
        }

        .el-icon-video-play {
          color: #4bc451;
        }
        .el-icon-video-pause {
          color: #f35353;
        }
        .el-icon-document-checked,
        .el-icon-s-promotion,
        .el-icon-document {
          color: #4381e6;
        }
      }
    }
  }

  // 加载状态
  .run-loadings {
    position: absolute;
    bottom: 0;
    width: 100%;
    height: calc(100% - 40px);
    z-index: 999;
    background: rgba($color: #000000, $alpha: 0.3);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: #462cf4;
    font-size: 12px;

    .el-icon-loading {
      font-size: 22px;
      margin-bottom: 10px;
      color: #462cf4;
    }
  }
}
</style>
